/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */


var EXPORTED_SYMBOLS =
[
    "ArrayTransformer",
    "ChainedTransformer",
    "CommandLabelTransformer",
    "DataSourceTransformer",
    "FolderEntryTransformer",
    "MaxLengthTransformer",
    "NamedFolderTransformer",
    "RecentFolderTransformer",
    "SwitchTransformer",
];

const Cu = Components.utils;

Cu.import("resource://savefileto/common.jsm");
Cu.import("resource://savefileto/Entry.jsm");


/**
 * Transformer that transforms input value using multiple transfomers in sequence.
 */
function ChainedTransformer(transformer /* ... */)
{
    this.transformers = Array.prototype.slice.call(arguments);
}

ChainedTransformer.prototype =
{
    transform: function(value)
    {
        for each (var transformer in this.transformers)
            value = transformer.transform(value);
        return value;
    }
}


/**
 * Transformer that applies |tranformer| to each element of input array.
 * Transforming results (scalar or array) are flattened into a single result array.
 * Errors from transformers can optionally be ignored.
 */
function ArrayTransformer(transformer, ignoreErrors)
{
    this.transformer = transformer;
    this.ignoreErrors = ignoreErrors;
}

ArrayTransformer.prototype =
{
    transform: function(arr)
    {
        var result = [];
        
        for each (var entry in arr)
            try
            {
                var value = this.transformer.transform(entry);
                if (Array.isArray(value))
                    Array.prototype.push.apply(result, value);
                else
                    result.push(value);
            }
            catch (e)
            {
                if (! this.ignoreErrors)
                    throw e;
                
                Cu.reportError(e);
            }
        
        return result;
    },
}


/**
 * Transforms array so that its length is |maxLength| or smaller.
 * If input array is larger, extra elements are trimmed away from the end.
 *
 * @param maxLength.value {int} maximum length of the returned array.
 */
function MaxLengthTransformer(maxLength)
{
    this.maxLength = maxLength;
}

MaxLengthTransformer.prototype =
{
    transform: function(arr)
    {
        var maxLength = this.maxLength.value;
        var result = ((arr.length <= maxLength) ? arr : arr.slice(0, maxLength));
        return result;
    }
}


/**
 * Transformer that transforms input using another transformer based on one of its property.
 */
function SwitchTransformer(prop, transformers)
{
    this.prop = prop;
    this.transformers = transformers;
}

SwitchTransformer.prototype =
{
    transform: function(entry)
    {
        if (! (entry && (this.prop in entry)))
            throw new Error("entry has no property: '" + this.prop + "' " + entry);
        
        var type = entry[this.prop];
        if (! (type in this.transformers))
            throw new Error("no transformer found for type '" + type + "'");
        
        var transformer = this.transformers[type];
        return (transformer ? transformer.transform(entry) : entry);
    }
}


/**
 * "Transformer" that returns data source's entries.
 */
function DataSourceTransformer(dataSource)
{
    this.dataSource = dataSource;
}

DataSourceTransformer.prototype =
{
    transform: function()
    {
        return this.dataSource.entries;
    }
}


/**
 * Transforms raw folder entry to FolderEntry.
 */
function FolderEntryTransformer(compare, filter)
{
    this.compare = compare;
    this.filter = filter;
}

FolderEntryTransformer.prototype =
{
    transform: function(entry)
    {
        return new FolderEntry(entry, this.compare, this.filter);
    }
}


/**
 * Transformer FolderEntry to raw folder entry.
 */
function RecentFolderTransformer()
{
    // empty
}

RecentFolderTransformer.prototype =
{
    transform: function(folder)
    {
        return {type: "folder", label: folder.pathLabel, path: folder.path, menu: false};
    }
}


/**
 * Transformer that creates raw folder entries for named folders.
 *
 * {value: <folder id>, menu: <menu>} ->
 * {type: "folder", menu: <menu>, path: <folder's path>, label: <label from bundle using folder id as the key>}
 * 
 * Note that transform() will throw,
 * if folder isn't valid or the bundle doesn't contain the key and throws.
 *
 * @param bundle {nsIStringBundle}
 */
function NamedFolderTransformer(bundle)
{
    this.bundle = bundle;
}

NamedFolderTransformer.prototype =
{
    transform: function(entry)
    {
        var value = entry.value;
        if (! value)
            throw new Error("entry has no value: " + entry);
        
        var file = getFile("$" + value);
        if (! file)
            throw new Error("invalid folder name: " + value);
        
        var label;
        try { label = this.bundle.GetStringFromName(value); }
        catch (e) { label = file.leafName; }
        
        entry =
        {
            type: "folder",
            label: label,
            path: file.path,
            menu: (!! entry.menu)
        }
        
        return entry;
    }
}


/**
 * Transformer that adds label for a command entry.
 *
 * {type: <type>, command: <command>} ->
 * {type: <type>, command: <command>, label: <get value from bundle using command as the key>}
 * 
 * Note that transform() will throw,
 * if the bundle doesn't contain the key and throws.
 *
 * @param bundle {nsIStringBundle}
 */
function CommandLabelTransformer(bundle)
{
    this.bundle = bundle;
}

CommandLabelTransformer.prototype =
{
    transform: function(entry)
    {
        if (! ("label" in entry))
        {
            entry =
            {
                type: entry.type,
                label: this.bundle.GetStringFromName(entry.command),
                command: entry.command
            }
        }
        
        return entry;
    }
}
